package com.ruoyi.common.utils;

import org.apache.commons.lang3.StringUtils;
import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Type;
import org.web3j.crypto.CipherException;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.WalletUtils;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.Web3jService;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.DefaultBlockParameterNumber;
import org.web3j.protocol.core.JsonRpc2_0Web3j;
import org.web3j.protocol.core.methods.request.EthFilter;
import org.web3j.protocol.core.methods.response.*;
import org.web3j.protocol.core.methods.response.EthBlock.TransactionResult;
import org.web3j.protocol.core.methods.response.EthLog.LogResult;
import org.web3j.protocol.http.HttpService;
import org.web3j.tx.RawTransactionManager;
import org.web3j.tx.Transfer;
import org.web3j.tx.gas.ContractGasProvider;
import org.web3j.utils.Convert;
import org.web3j.utils.Convert.Unit;
import org.web3j.utils.Numeric;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;

public class Web3Utils {

    //用来标志是否是以太的充值
    public static final String ETH_FLAG = "ETH";

    private static Web3j web3j = null;
    //private static final String url = "https://ropsten.infura.io/v3/b0eb6154423346b781463a85b6563092";
    private static String url = "https://mainnet.infura.io/v3/b0eb6154423346b781463a85b6563092";
    private static Map<String, String> addrmap = new ConcurrentHashMap<>();
    private static List<String> contractAddrList = null;

    public static void init() throws IOException {
        //web3j = build(new HttpService("http://127.0.0.1:8888/"));
        web3j = build(new HttpService(url));
        //viewSyncing(web3j);
    }

    public static String getUrl() {
        return url;
    }

    public static void setUrl(String url) {
        Web3Utils.url = url;
    }

    public static Web3j web3() {
        return web3j;
    }

    private static Web3j build(Web3jService web3jService) {
        return new JsonRpc2_0Web3j(web3jService);
    }

    public static void setContractAddrList(List<String> _contractAddrList) {
        contractAddrList = _contractAddrList;
    }

    public static void putAddrMap(String addr, String userId) {
        addrmap.put(addr, userId);
    }

    public static Res getChargeInfos(Res res) {
        try {
            EthSyncing ethSyncing = web3j.ethSyncing().send();
            if (ethSyncing.isSyncing()) {
                System.out.println("正在同步区块。。。");
                //LogServer.error(Web3.class, "正在同步区块");
                return null;
            }
            res.ents = new ArrayList<Ent>();
            //res.beginBlock = beginBlock;
            //res.endBlock = beginBlock.add(new BigInteger("20"));
            res.addrmap = addrmap;
            res.contractAddresss = contractAddrList;
            //LogServer.error(Web3.class, res.toString());
            getAllLogByContractAddress(res);
			getBlockForEth(res);

            return res;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }

    //一键汇总
    public static void oneKeyEth(String genDir, List<String> fileList, String password, String toTagetAddress) {
        //开始汇总所有账户

    }

    //一键汇总合约币
    public static void oneKeyContract(String genDir, List<String> fileList, String password, String toTagetAddress, String contractAddress) {
        //开始汇总所有账户

    }

    public static BigInteger queryAccountAmount(String address) throws IOException {
        EthBlockNumber blockNumber = web3j.ethBlockNumber().send();
        //System.out.println(blockNumber.getBlockNumber());

        return web3j.ethGetBalance(address, new DefaultBlockParameterNumber(blockNumber.getBlockNumber())).send().getBalance();
    }

    public static String[] genWalletAndReturnAddress(String genDir, String password) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException, CipherException, IOException {
        String fileName = newWallet(password, genDir);
        Credentials credentials = WalletUtils.loadCredentials(password, genDir + fileName);
        return new String[]{fileName, credentials.getAddress()};
    }

    public static String newWallet(String password, String genDir) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException, CipherException, IOException {
        String fileName = WalletUtils.generateFullNewWalletFile(password, new File(genDir));
        return fileName;
    }


    public static void getAllLogByContractAddress(Res res) throws Exception {

        long beginBlock = res.beginBlock.longValue();
        long endBlock = res.endBlock.longValue();
        EthFilter filter = new EthFilter(new DefaultBlockParameterNumber(beginBlock), new DefaultBlockParameterNumber(endBlock), res.contractAddresss);

        filter.addOptionalTopics("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef");
        EthLog ethLog = web3j.ethGetLogs(filter).send();
        List<LogResult> logResults = ethLog.getLogs();
        System.out.println("过滤日志对象:" + logResults);
        if (logResults != null)
            for (LogResult log : logResults) {
                Log _log = (Log) log.get();
                List<String> topics = _log.getTopics();
                String from = topics.get(1).replace("000000000000000000000000", "");
                String to = topics.get(2).replace("000000000000000000000000", "");
                if (!res.addrmap.containsKey(to)) continue;
                if (!checkStatus(_log.getTransactionHash())) continue;
                res.ents.add(new Ent(_log.getTransactionHash(), from.toLowerCase(), to.toLowerCase(), bigInteger2BigDecimal(Numeric.decodeQuantity(_log.getData())), _log.getAddress().toLowerCase()));
                System.out.println(_log.getAddress() + " =>> " + topics.get(1) + " -> " + topics.get(2) + " = " + Numeric.decodeQuantity(_log.getData()));
            }

    }

    public static void getBlockForEth(Res res) throws Exception {
        long beginBlock = res.beginBlock.longValue();
        long endBlock = res.endBlock.longValue();

        for (long i = beginBlock; i <= endBlock; i++) {
//            System.out.println("执行块高:" + i);

            EthBlock b = web3j.ethGetBlockByNumber(new DefaultBlockParameterNumber(i), true).send();

            //LogServer.error(Web3.class, "事务对象：" + b.getBlock().getTransactions());
            for (TransactionResult r : b.getBlock().getTransactions()) {

                Transaction t = ((Transaction) r.get());
                if (t.getValue().longValue() <= 0) continue;

                //System.out.println(t.getFrom() + "======" + t.getHash());
                if (StringUtils.isBlank(t.getTo())) continue;
                if (!res.addrmap.containsKey(t.getTo())) continue;
                if (!checkStatus(t.getHash())) continue;

                res.ents.add(new Ent(t.getHash(), t.getFrom().toLowerCase(), t.getTo().toLowerCase(), wei2Ether(t.getValue()), Web3Utils.ETH_FLAG));
                System.out.println(t.getHash() + "===" + wei2Ether(t.getValue()));
            }

        }
    }

    public static String approve(Credentials credentials,
                                 ContractGasProvider contractGasProvider, String spender, BigInteger amount, String contract, BigInteger gasPrice, BigInteger gasLimit)
            throws Exception {
        Function function = new Function(
                "approve",
                Arrays.<Type>asList(new org.web3j.abi.datatypes.Address(spender),
                        new org.web3j.abi.datatypes.generated.Uint256(amount)),
                Collections.<TypeReference<?>>emptyList());
        return sendRawTransaction(credentials, function, contract, gasPrice, gasLimit);
    }


    public static String transferFrom(Credentials credentials, String from, String to, BigInteger amount, String contract, BigInteger gasPrice, BigInteger gasLimit)
            throws Exception {
        Function function = new Function(
                "transferFrom",
                Arrays.<Type>asList(new org.web3j.abi.datatypes.Address(from),
                        new org.web3j.abi.datatypes.Address(to),
                        new org.web3j.abi.datatypes.generated.Uint256(amount)),
                Collections.<TypeReference<?>>emptyList());
        return sendRawTransaction(credentials, function, contract, gasPrice, gasLimit);
    }

    public static String transfer(Credentials credentials, String to, BigInteger amount, String contract, BigInteger gasPrice, BigInteger gasLimit)
            throws Exception {
        final Function function = new Function(
                "transfer",
                Arrays.<Type>asList(new org.web3j.abi.datatypes.Address(to),
                        new org.web3j.abi.datatypes.generated.Uint256(amount)),
                Collections.<TypeReference<?>>emptyList());
        return sendRawTransaction(credentials, function, contract, gasPrice, gasLimit);
    }

    private static String sendRawTransaction(Credentials credentials, Function function, String to, BigInteger gasPrice, BigInteger gasLimit) throws InterruptedException, ExecutionException, IOException {
        EthGetTransactionCount ethGetTransactionCount = web3j.ethGetTransactionCount(credentials.getAddress(), DefaultBlockParameterName.LATEST).sendAsync().get();
        BigInteger nonce = ethGetTransactionCount.getTransactionCount();
        String data = FunctionEncoder.encode(function);
        RawTransaction rawTransaction = RawTransaction.createTransaction(
                nonce,
                gasPrice,
                gasLimit,
                to,
                data);
        RawTransactionManager manager = new RawTransactionManager(web3j, credentials, 10, 15000);
        EthSendTransaction sendTransaction = manager.signAndSend(rawTransaction);
        if (sendTransaction.hasError()) {
            throw new RuntimeException("Error processing transaction request: "
                    + sendTransaction.getError().getMessage());
        }
        return sendTransaction.getTransactionHash();
    }

    public static BigInteger balanceOf(String address, String contract) throws ExecutionException, InterruptedException {
        Function function = new Function(
                "balanceOf",
                Arrays.asList(new Address(address)),  // Solidity Types in smart contract functions
                Arrays.asList(new TypeReference<Type>() {
                }));
        String encodedFunction = FunctionEncoder.encode(function);
        org.web3j.protocol.core.methods.response.EthCall response = web3j.ethCall(
                org.web3j.protocol.core.methods.request.Transaction.createEthCallTransaction(address, contract, encodedFunction),
                DefaultBlockParameterName.LATEST)
                .sendAsync().get();
        String returnValue = response.getValue(); //返回16进制余额
        returnValue = returnValue.substring(2);
        BigInteger balance = new BigInteger(returnValue, 16);
        return balance;
    }

    public static long getLastestBlockHeight() throws Exception {
        EthBlockNumber blockNumber = web3j.ethBlockNumber().send();
        if (blockNumber.hasError()) {
            throw new Exception(blockNumber.getError().getMessage());
        }
        long lastest = blockNumber.getBlockNumber().longValue();
        return lastest;
    }

    public static TransactionReceipt receipt(String hash) throws IOException {
        EthGetTransactionReceipt ethReceiptEth = web3j.ethGetTransactionReceipt(hash).send();
        return ethReceiptEth.getResult();
    }

    public static boolean checkStatus(String txHash) throws IOException {
        TransactionReceipt re = receipt(txHash);
        return re.getStatus().equals("0x1");
    }

    public static BigDecimal wei2Ether(String val) {
        return Convert.fromWei(new BigDecimal(val), Unit.ETHER);
    }

    public static BigDecimal wei2Ether(BigInteger val) {
        return Convert.fromWei(new BigDecimal(val.toString()), Unit.ETHER);
    }

    public static BigDecimal bigInteger2BigDecimal(BigInteger val) {
        return new BigDecimal(val.toString());
    }

    /**
     * 发起一笔交易
     * 使用以太钱包文件发送以太币给其他人，不能设置nonce：（推荐）
     *
     * @param privateKey
     */
    public static String transfer(String toAddress, BigDecimal value, Unit unit, String privateKey) throws Exception {
        Credentials credentials = Credentials.create(privateKey);
        TransactionReceipt transactionReceipt = Transfer.sendFunds(web3j, credentials, toAddress, value, unit).sendAsync().get();
        String transactionHash = transactionReceipt.getTransactionHash();
        return transactionHash;
    }

    public static class Ent {

        public Ent(String txHash, String from, String to, BigDecimal val, String contractAddress) {
            this.txHash = txHash;
            this.from = from;
            this.to = to;
            this.val = val;
            this.contractAddress = contractAddress;
        }

        private String txHash;
        private String from;
        private String to;
        private BigDecimal val;
        private String contractAddress;
        private long uID;

        public String getTxHash() {
            return txHash;
        }

        public void setTxHash(String txHash) {
            this.txHash = txHash;
        }

        public String getFrom() {
            return from;
        }

        public void setFrom(String from) {
            this.from = from;
        }

        public String getTo() {
            return to;
        }

        public void setTo(String to) {
            this.to = to;
        }

        public BigDecimal getVal() {
            return val;
        }

        public void setVal(BigDecimal val) {
            this.val = val;
        }

        public String getContractAddress() {
            return contractAddress;
        }

        public void setContractAddress(String contractAddress) {
            this.contractAddress = contractAddress;
        }

        public long getuID() {
            return uID;
        }

        public void setuID(long uID) {
            this.uID = uID;
        }
    }

    public static class Res {
        public Map<String, String> addrmap;
        public BigInteger beginBlock;
        public BigInteger endBlock;
        public List<String> contractAddresss;
        public List<Ent> ents;

    }

    public static interface IEthDepositCall {
        public void call(Res res);
    }

    public static void confirmEndBlock(Res res) throws Exception {
        res.endBlock = res.beginBlock.add(new BigInteger("100"));
        //EthBlockNumber blockNumber = web3j.ethBlockNumber().send();
        long lastest = getLastestBlockHeight();
        lastest = lastest - 1;
        if (res.endBlock.longValue() > lastest) res.endBlock = new BigInteger(lastest + "");
    }

    public static Map<String, String> getAddrmap() {
        return addrmap;
    }

    public static void main(String[] args) {
        try {
            String contractAddress = "0xdDA7F48d5068982F6A9B485A01073d6b654d1d4F";
            Web3Utils.setUrl("https://ropsten.infura.io/v3/b0eb6154423346b781463a85b6563092");
//            //TODO 获取所有合约地址列表，状态为0的合约地址列表
//            List<String> sList = new ArrayList<>();
//            sList.add(contractAddress);
//            Web3Utils.setContractAddrList(sList);
            Web3Utils.init();
//            BigInteger ethBalance = Web3Utils.queryAccountAmount("0x985f94F66098F4E51d766a02717554c098d7498c");
//            System.out.println(ethBalance);
//            Credentials credentials = Credentials.create(AESUtil.decrypt("662b7854ecc650965a6796761e0c926d90d6cec68b5ec182bc7a6d14438e61f969ea197b53fc99429d63636bac2e799a20a402efef5887288cae21a3001713ee612f0027ee1809bd0e1a5d97cc55eded"));
            System.out.println(Web3Utils.balanceOf("0x985f94F66098F4E51d766a02717554c098d7498c", "0x6020fe1c103165d3cb4b6a7bf0c33757a069fdfc"));
//			Credentials credentials = WalletUtils.loadCredentials("123456", "E:/ethwallet/UTC--2019-08-19T07-52-56.983000000Z--8d8d56004a9cc55cc4210da23c66df30a37bb6c9.json");
//            String txnHash = Web3Utils.approve(credentials, new DefaultGasProvider(),"0x985f94F66098F4E51d766a02717554c098d7498c", new BigInteger("0"), contractAddress);
//            System.out.println(txnHash);
//            Credentials credentials = Credentials.create("01105D7A5C6CFF39E75522D511FD8E8353C2E2B6E04753A8CAD13FE3A7EBC903");
//            Credentials credentials = WalletUtils.loadCredentials("123456", "E:/ethwallet/UTC--2019-08-19T07-52-56.983000000Z--8d8d56004a9cc55cc4210da23c66df30a37bb6c9.json");
//            String txnHash = Web3Utils.transferFrom(credentials, new DefaultGasProvider(), "0x8d8d56004a9cc55cc4210da23c66df30a37bb6c9", "0x5124763a22ea964bc38abd05ce66fa69a8d96b08", new BigInteger("8000000"), contractAddress);
//            Web3Utils.checkStatus("0x725b5c84ca5db428a5cb5da3ed1164474bd71e07ba667f2020e74cbacdc45b14");
//            String txnHash = Web3Utils.transfer(credentials, "0x985f94F66098F4E51d766a02717554c098d7498c", new BigInteger("1000000000"), contractAddress, Convert.toWei("10", Convert.Unit.GWEI).toBigInteger(), new BigInteger("60000"));
//			System.out.println(txnHash);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
